import React, {MouseEvent, WheelEvent} from "react";
import {FlowItem, FlowItemParams, POINTER_SIZE} from "./FlowItem";
import {LeftOutlined, PlusOutlined, RightOutlined} from "@ant-design/icons";
import "./index.css"
import {Avatar, Button, Drawer, List} from "antd";

export interface FlowItemNodeIns {
    id: string;
    title: string;
    description: string;
    x: number;
    y: number;
    width: number;
    height?: number;
    inputs: FlowItemParams[];
    outputs: FlowItemParams[];
    inCount: number; // 输入插槽数
    outCount: number; // 输出插槽数
    editable?: boolean;
}

export interface FlowItemNodeLine {
    startIndex: number;
    startId: string;
    endIndex: number;
    endId: string;
}


export interface CozeWorkflowProps {
    title: string;
    description: string;
    version: number;
    width?: number | string;
    height?: number | string;
    x?: number;
    y?: number;
    scale?: number;
    nodes: FlowItemNode[];
    items?: FlowItemNodeIns[]; // 已经设置的节点
    lines?: FlowItemNodeLine[]; // 已经链接的线
    Save?: (version: number, x: number, y: number, scale: number, items: FlowItemNodeIns[], lines: FlowItemNodeLine[]) => void
}

export interface FlowItemNode {
    id: string;
    title: string;
    description: string;
    item: FlowItemNodeIns;
}

export interface FlowItemObj {
    id: string;
    title: string;
    description: string;
    active: boolean;
    x: number;
    y: number;
    width: number;
    height?: number;
    inputs: FlowItemParams[];
    outputs: FlowItemParams[];
    inCount: number; // 输入插槽数
    outCount: number; // 输出插槽数
    lines: {
        [key in string]: Line
    }
    editable?: boolean;
    ref?: HTMLDivElement;

}

interface Line {
    startX: number,
    startY: number,
    endX: number,
    endY: number,
    startIn?: boolean,
    start?: FlowItemObj,
    start_index: number,
    end?: FlowItemObj,
    end_index: number,
}

export class CozeWorkflow extends React.Component<CozeWorkflowProps, any> {
    container: HTMLDivElement | null = null;
    currentScale: number = 1;
    left: number = 0;
    top: number = 0;

    move: {
        x: number,
        y: number,
        startX: number,
        startY: number,
        moveItem?: FlowItemObj,
        line?: {
            startItem: FlowItemObj,
            isInput: boolean,
            index: number,
            endPoint?: {
                item: FlowItemObj,
                isInput: boolean,
                index: number,
            },
        }
    } | null = null;

    currentLine: Line | null = null;
    lines: {
        [key in string]: Line
    } = {}
    state: {
        menu_open: boolean,
        items: { [id in string]: FlowItemObj },
        version: number
    }

    flowItemPoint: {
        [id in string]: {
            true: HTMLElement[],
            false: HTMLElement[]
        }
    } = {}
    isInitialized: boolean = false;


    constructor(props: CozeWorkflowProps) {
        super(props);
        this.handleWheel = this.handleWheel.bind(this)
        this.handleMoveStart = this.handleMoveStart.bind(this)
        this.handleMove = this.handleMove.bind(this)
        this.handleMoveEnd = this.handleMoveEnd.bind(this)
        this.cancelAllActive = this.cancelAllActive.bind(this)
        this.renderLines = this.renderLines.bind(this)
        this.renderLine = this.renderLine.bind(this)
        this.getPoint = this.getPoint.bind(this)
        this.handleChange = this.handleChange.bind(this)
        this.findBeforeParams = this.findBeforeParams.bind(this)
        const items: any = {}
        props.items?.forEach(item => {
            items[item.id] = {
                ...item,
                active: false,
                ref: null,
                lines: {},
            }
        })
        this.left = props.x || 0
        this.top = props.y || 0
        this.currentScale = props.scale || 1

        this.state = {
            menu_open: false,
            items: items,
            version: props.version
        }

    }

    getPoint(id: string, isInput: boolean, index: number): HTMLElement | null {
        if (!this.flowItemPoint[id]) {
            return null
        }
        // @ts-ignore
        if (!this.flowItemPoint[id][isInput][index]) {
            return null
        }
        // @ts-ignore
        return this.flowItemPoint[id][isInput][index]
    }

    handleMoveStart(e: MouseEvent) {
        this.move = {
            x: e.clientX,
            y: e.clientY,
            startX: this.left,
            startY: this.top,
        }
    }

    handleMove(e: MouseEvent) {
        if (!this.container) {
            return
        }
        if (!this.move) {
            return;
        }
        if (this.move.moveItem) {
            this.move.moveItem.x = this.move.startX + (e.clientX - this.move.x) * (1 / this.currentScale)
            this.move.moveItem.y = this.move.startY + (e.clientY - this.move.y) * (1 / this.currentScale)
            if (this.move.moveItem.ref) {
                this.move.moveItem.ref.style.left = this.move.moveItem.x + "px"
                this.move.moveItem.ref.style.top = this.move.moveItem.y + "px"
            }
            const c = this.container.getBoundingClientRect();
            for (const key in this.move.moveItem.lines) {
                const line = this.move.moveItem.lines[key];
                if (!line.start) {
                    continue
                }

                const start = this.getPoint(line.start?.id, false, line.start_index)
                if (!start) {
                    continue
                }
                if (!line.end) {
                    continue
                }
                const end = this.getPoint(line.end?.id, true, line.end_index)
                if (!end) {
                    continue
                }
                const sb = start.getBoundingClientRect();
                const eb = end.getBoundingClientRect();
                if (!sb || !eb) {
                    continue
                }
                line.startX = (sb.x - c.x) / this.currentScale + POINTER_SIZE
                line.startY = (sb.y - c.y) / this.currentScale + POINTER_SIZE
                line.endX = (eb.x - c.x) / this.currentScale + POINTER_SIZE
                line.endY = (eb.y - c.y) / this.currentScale + POINTER_SIZE
            }
            this.setState({})
            this.handleChange()
            return;
        } else if (this.move.line) {
            const c = this.container.getBoundingClientRect();
            const x = (e.clientX - c.x) / this.currentScale;
            const y = (e.clientY - c.y) / this.currentScale;
            if (this.currentLine) {
                this.currentLine.endX = x
                this.currentLine.endY = y
                this.move.line.endPoint = undefined
                this.setState({})
            }
            return;
        }
        this.left = this.move.startX + e.clientX - this.move.x
        this.top = this.move.startY + e.clientY - this.move.y

        this.container.style.left = this.left + "px"
        this.container.style.top = this.top + "px"
        this.handleChange()
        return
    }

    handleMoveEnd(e: MouseEvent) {
        if (this.move && this.move.line) {
            if (this.move.line.endPoint && this.currentLine) {
                const start = this.move.line.startItem;
                const end = this.move.line.endPoint.item;
                let key = `${end.id}:${this.move.line.endPoint.index}-${start.id}:${this.move.line.index}`
                if (!this.move.line.isInput) {
                    key = `${start.id}:${this.move.line.index}-${end.id}:${this.move.line.endPoint.index}`
                    this.currentLine.start = this.move.line.startItem
                    this.currentLine.end = this.move.line.endPoint.item
                    this.currentLine.start_index = this.move.line.index
                    this.currentLine.end_index = this.move.line.endPoint.index
                } else {
                    this.currentLine.end = this.move.line.startItem
                    this.currentLine.start = this.move.line.endPoint.item
                    this.currentLine.start_index = this.move.line.endPoint.index
                    this.currentLine.end_index = this.move.line.index
                }

                this.lines[key] = this.currentLine;
                start.lines[key] = this.currentLine;
                end.lines[key] = this.currentLine;
                this.handleChange()
            }
            this.currentLine = null
            this.setState({})

        }
        this.move = null
    }

    handleWheel(e: WheelEvent) {
        if(this.state.menu_open) {
            return
        }
        // @ts-ignore
        const clzName: string = e.target.className;
        if (clzName.startsWith("ant-select-item") || clzName.includes("scrollbar")) {
            return
        }
        if (this.container?.style) {
            // 判断 command 键是否被按下

            let old = this.currentScale
            // @ts-ignore
            e.startScale = old;
            let scale = -e.deltaY / 1000
            let current = old + (old * scale);
            if (current < 0.3) {
                current = 0.3
            } else if (current > 10) {
                current = 10
            }
            this.currentScale = current
            this.container.style.scale = `${this.currentScale}`
            scale = current / old
            const x = e.clientX - this.left
            const y = e.clientY - this.top
            this.left = this.left + x - x * scale
            this.top = this.top + y - y * scale
            this.container.style.left = this.left + "px"
            this.container.style.top = this.top + "px"
            this.handleChange()
        }

    }

    componentDidMount() {
        // @ts-ignore
        document.addEventListener("mousemove", this.handleMove)
        // @ts-ignore
        document.addEventListener("mouseup", this.handleMoveEnd)
        if (this.isInitialized) {
            return
        }
        this.isInitialized = true
        this.props.lines?.forEach(line => {
            const start = this.getPoint(line.startId, false, line.startIndex);
            const end = this.getPoint(line.endId, true, line.endIndex);
            if (!start || !end) {
                return
            }
            const s = start.getBoundingClientRect()
            const e = end.getBoundingClientRect()
            const key = line.startId + ":" + line.startIndex + "-" + line.endId + ":" + line.endIndex;
            this.lines[key] = {
                startX: s.x,
                startY: s.y,
                endX: e.x,
                endY: e.y,
                startIn: false,
                start_index: line.startIndex,
                end_index: line.endIndex,
                start: this.state.items[line.startId],
                end: this.state.items[line.endId],
            }
            this.state.items[line.startId].lines[key] = this.lines[key]
            this.state.items[line.endId].lines[key] = this.lines[key]
        })
        if (this.container) {
            this.container.style.left = this.left + "px"
            this.container.style.top = this.top + "px"
            this.container.style.scale = `${this.currentScale}`
        }

        this.setState({})
    }

    componentWillUnmount() {
        // @ts-ignore
        document.removeEventListener("mousemove", this.handleMove)
        // @ts-ignore
        document.removeEventListener("mouseup", this.handleMoveEnd)
    }

    handleChange() {
        if (this.state.version !== this.props.version) {
            return
        }
        this.state.version++
    }

    cancelAllActive() {
        for (const o in this.state.items) {
            this.state.items[o].active = false;
        }
    }

    renderLine(line: Line, index: number) {


        const x = Math.min(line.startX, line.endX)
        const y = Math.min(line.startY, line.endY)
        let difY = 0 // Math.max(0.1 * Math.abs(line.endY - line.startY), 120)
        let difX = Math.min(Math.max(0.3 * Math.abs(line.endX - line.startX), 200), 120)
        if (line.startIn) {
            difX = -difX
        }
        if (line.endY < line.startY) {
            difY = -difY
        }
        return <svg key={index} style={{
            position: "absolute",
            left: x,
            top: y,
            overflow: 'visible',

        }}
        >
            <path
                stroke="var(  --light-usage-primary-color-primary)"
                strokeOpacity="1" fill="none"
                pointerEvents="visibleStroke"
                fillOpacity="1"
                className={`line selected`}
                onMouseDown={(e) => {
                    e.stopPropagation()
                    if (!line.start || !line.end) {
                        return
                    }
                    this.move = {
                        line: {
                            startItem: line.start,
                            isInput: false,
                            index: line.start_index,
                            endPoint: {
                                item: line.end,
                                isInput: true,
                                index: line.end_index
                            }
                        },
                        startX: e.clientX,
                        startY: e.clientY,
                        x: e.clientX,
                        y: e.clientY
                    }
                    this.currentLine = line
                    delete this.lines[`${line.start.id}:${line.start_index}-${line.end.id}:${line.end_index}`]
                    this.setState({})
                }}
                style={{strokeWidth: 2, cursor: "move"}}
                d={`M ${line.startX - x} ${line.startY - y} C ${line.startX - x + difX} ${line.startY - y + difY} ${line.endX - x - difX}  ${line.endY - y - difY} ${line.endX - x} ${line.endY - y}`}></path>
            <path
                d={`M ${line.endX - x - 8},${line.endY - y - 4} L ${line.endX - x - 2},${line.endY - y} L ${line.endX - x - 8},${line.endY - y + 4}`}
                strokeLinecap="round"
                stroke="var(  --light-usage-primary-color-primary)" fill="none" strokeWidth="3"></path>
        </svg>
    }

    renderLines() {
        return <>
            {Object.values(this.lines).map(this.renderLine)}
            {this.currentLine && this.renderLine(this.currentLine, -1)}
        </>
    }

    findBeforeParams(item: FlowItemObj, filter: string, type: string, times = 0): string[] {
        if (times > 90) {
            return []
        }
        const params = []
        for (const k in item.lines) {

            const line = item.lines[k]
            if (!line.start) {
                continue
            }
            if (!this.lines[k]) { // 这条线已经被删除了
                continue
            }
            if (line.start.id === item.id) {
                continue
            }
            const a = this.findBeforeParams(line.start, filter, type, times + 1)
            params.push(...a)
            if (filter) {
                if (!line.start.title.startsWith(filter)) {
                    continue
                }
            }
            for (const out of line.start.outputs) {
                if (out.type !== type) {
                    continue
                }
                params.push(`$${line.start.title}.${out.name}`)
            }

        }
        return params
    }

    render() {

        return (
            // @ts-ignore
            <div className="coze-workflow" onWheel={this.handleWheel}
                 onMouseDown={this.handleMoveStart}
                 onClick={(e) => {
                     this.cancelAllActive();
                     this.setState({})
                 }}
                 style={{
                     width:
                     this.props.width,
                     height: this.props.height,
                     overflow: "hidden",
                     cursor: "pointer",
                     position: "relative"
                 }}>
                <Button className={"node-save-btn"} type={"primary"} onClick={(e) => {
                    if (!this.props.Save) {
                        return
                    }
                    const items: FlowItemNodeIns[] = Object.values(this.state.items).map(({
                                                                                              active,
                                                                                              ref,
                                                                                              lines,
                                                                                              ...item
                                                                                          }) => {
                        return item
                    })
                    const lines: FlowItemNodeLine[] = Object.values(this.lines).map((item) => {
                        return {
                            startId: item.start?.id || "",
                            startIndex: item.start_index,
                            endId: item.end?.id || "",
                            endIndex: item.end_index
                        }
                    })
                    this.props.Save(this.state.version, this.left, this.top, this.currentScale, items, lines)
                }}
                        disabled={this.state.version === this.props.version}>
                    保存
                </Button>
                <div className={"coze-workflow-menu-silent"}>
                    <Drawer
                        title="添加节点"
                        placement={"left"}
                        closable={false}
                        onClose={() => {
                            this.setState({menu_open: false})
                        }}
                        open={this.state.menu_open}
                    >
                        <List
                            dataSource={this.props.nodes}
                            renderItem={(item, index) => (
                                <List.Item>
                                    <List.Item.Meta
                                        avatar={<Avatar>{item.title.slice(0, 1)}</Avatar>}
                                        title={item.title}
                                        description={item.description}
                                    />
                                    <Button type={"primary"} onClick={() => {
                                        const obj: FlowItemObj = Object.assign({
                                            active: false,
                                        }, item.item, {
                                            x: 120,
                                            id: item.item.id + "-" + (Date.now() + Math.random()),
                                            inputs: [...item.item.inputs],
                                            outputs: [...item.item.outputs],
                                            lines: {},
                                        })
                                        this.state.items[obj.id] = obj
                                        this.setState({})
                                        this.handleChange()
                                    }}>
                                        <PlusOutlined className={"node-add-btn"}/>
                                    </Button>

                                </List.Item>
                            )}
                        />
                    </Drawer>
                    {this.state.menu_open ? <LeftOutlined className="coze-workflow-menu-icon" onClick={(e) => {
                        this.setState({menu_open: false})
                    }}/> : <RightOutlined className="coze-workflow-menu-icon" onClick={(e) => {
                        this.setState({menu_open: true})
                    }}/>}
                </div>
                <div className={"coze-workflow-container"}
                     style={{position: "absolute"}}
                     ref={(ref) => {
                         this.container = ref;
                         // @ts-ignore
                         window.container = ref;
                     }}>
                    {
                        this.renderLines()
                    }
                    {
                        Object.keys(this.state.items).map((index) => {
                            const item = this.state.items[index]
                            return <FlowItem key={index} inputs={item.inputs} outputs={item.outputs} title={item.title}
                                             active={item.active}
                                             inCount={item.inCount}
                                             outCount={item.outCount}
                                             description={item.description}
                                             editable={item.editable}
                                             OnInputChange={(inputs) => {
                                                 item.inputs = inputs
                                                 this.handleChange()
                                             }}
                                             handleValueSelect={(value, type) => {
                                                 return this.findBeforeParams(item, value.slice(1), type)
                                             }}
                                             OnOutputChange={(outputs) => {
                                                 item.outputs = outputs
                                                 this.handleChange()
                                             }}
                                             onClick={(e: MouseEvent) => {
                                                 //@ts-ignore
                                                 if (item.hasMove) {
                                                     return
                                                 }
                                                 this.cancelAllActive()
                                                 item.active = true;
                                                 e.stopPropagation()

                                                 this.setState({})
                                             }}
                                             OnTitleChange={(title) => {
                                                 item.title = title
                                                 this.setState({})
                                                 this.handleChange()
                                             }}
                                             OnDelete={() => {
                                                 delete this.state.items[index]
                                                 delete this.flowItemPoint[item.id]
                                                 if (item.lines && Object.keys(item.lines).length > 0) {
                                                     for (let key in item.lines) {
                                                         delete this.lines[key]
                                                     }
                                                 }
                                                 this.setState({})
                                                 this.handleChange()
                                             }}
                                             handleRef={(ref) => item.ref = ref}
                                             onMouseDown={(e: MouseEvent) => {
                                                 this.move = {
                                                     x: e.clientX,
                                                     y: e.clientY,
                                                     startX: item.x,
                                                     startY: item.y,
                                                     moveItem: item,
                                                 }
                                                 // e.preventDefault()
                                                 e.stopPropagation()
                                             }}
                                             pointerRef={(ref, isInput, index) => {
                                                 this.flowItemPoint[item.id] = this.flowItemPoint[item.id] || {
                                                     true: [],
                                                     false: [],
                                                 }
                                                 // @ts-ignore
                                                 this.flowItemPoint[item.id][isInput][index] = ref
                                             }}
                                             onPointerClick={(e, isInput, index) => {
                                                 if (!this.container) {
                                                     return
                                                 }
                                                 let target: HTMLElement = e.target as HTMLElement;
                                                 if (target.tagName === "DIV" && target.parentElement) {
                                                     target = target.parentElement;
                                                 }
                                                 const b = target.getBoundingClientRect();
                                                 const c = this.container.getBoundingClientRect();
                                                 const x = (b.x - c.x) / this.currentScale + POINTER_SIZE;
                                                 const y = (b.y - c.y) / this.currentScale + POINTER_SIZE;
                                                 this.move = {
                                                     x: e.clientX,
                                                     y: e.clientY,
                                                     startX: x,
                                                     startY: y,
                                                     line: {
                                                         startItem: item,
                                                         isInput,
                                                         index
                                                     },
                                                 }
                                                 // console.log(`b  --> ${b.x} ${b.y}`)
                                                 this.currentLine = {
                                                     startX: x,
                                                     startY: y,
                                                     endX: x,
                                                     endY: y,
                                                     startIn: isInput,
                                                     start_index: index,
                                                     end_index: -1,
                                                 }
                                                 e.stopPropagation()
                                             }}
                                             onPointerMove={(e, isInput, index) => {
                                                 // console.log("")
                                                 if (!this.container) {
                                                     return
                                                 }
                                                 if (!this.currentLine) {
                                                     return;
                                                 }
                                                 if (!this.move?.line) {
                                                     return;
                                                 }

                                                 // 同向不能链接
                                                 if (this.move.line.isInput === isInput) {
                                                     return;
                                                 }
                                                 if (this.move.line.startItem === item) {
                                                     return;
                                                 }
                                                 let target: HTMLElement = e.target as HTMLElement;
                                                 if (target.tagName === "DIV" && target.parentElement) {
                                                     target = target.parentElement;
                                                 }
                                                 const b = target.getBoundingClientRect();
                                                 const c = this.container.getBoundingClientRect();
                                                 const x = (b.x - c.x) / this.currentScale + POINTER_SIZE;
                                                 const y = (b.y - c.y) / this.currentScale + POINTER_SIZE;
                                                 this.currentLine.endX = x;
                                                 this.currentLine.endY = y;
                                                 this.move.line.endPoint = {
                                                     item: item,
                                                     index,
                                                     isInput
                                                 }
                                                 e.stopPropagation()
                                                 this.setState({})
                                             }}
                                             style={{
                                                 left: item.x,
                                                 top: item.y,
                                                 position: "absolute",
                                                 width: item.width,
                                                 height: item.height
                                             }}/>
                        })
                    }


                </div>

            </div>
        )
    }
}
